﻿using System;
using System.Buffers.Binary;
using System.Text;

namespace WholeSlideImageLibrary.Formats.OptimizedPyramidTiledImage
{
#pragma warning disable CA1815 // 重写值类型上的 Equals 和相等运算符
    public readonly struct FileMetadataItem
#pragma warning restore CA1815 // 重写值类型上的 Equals 和相等运算符
    {
        public FileMetadataItem(string name, string inlinedValue)
        {
            Name = name;
            ValueFieldLength = 0;
            InlinedValue = inlinedValue;
            ValuePointer = 0;
        }

        public FileMetadataItem(string name, ushort valueFieldLength, long valuePointer, long totalFileSize)
        {
            Name = name;
            ValueFieldLength = valueFieldLength;
            InlinedValue = null;
            ValuePointer = valuePointer < 0 ? valuePointer + totalFileSize : valuePointer;
        }

        public string Name { get; }
        public string? InlinedValue { get; }
        public long ValuePointer { get; }
        public ushort ValueFieldLength { get; }

        public bool IsInlined => !(InlinedValue is null);

        public int GetSize()
        {
            string name = Name ?? string.Empty;
            return 11 + Encoding.UTF8.GetByteCount(name);
        }

        public static bool TryParse(ReadOnlySpan<byte> buffer, long totalFileSize, out FileMetadataItem metadataItem, out int bytesConsumed)
        {
            int consumed = 0;
            if (buffer.IsEmpty)
            {
                goto Failure;
            }
            int length = buffer[0];
            buffer = buffer.Slice(1);
            consumed++;
            if (buffer.Length < length)
            {
                goto Failure;
            }
            string name = EncodingHelper.DecodeUtf8(buffer.Slice(0, length));
            buffer = buffer.Slice(length);
            consumed += length;
            if (buffer.Length < 10)
            {
                goto Failure;
            }
            ushort valueLength = BinaryPrimitives.ReadUInt16LittleEndian(buffer);
            consumed += 10;
            if (valueLength <= 8)
            {
                metadataItem = new FileMetadataItem(name, EncodingHelper.DecodeUtf8(buffer.Slice(2, valueLength)));
                bytesConsumed = consumed;
                return true;
            }
            else
            {
                long valuePointer = BinaryPrimitives.ReadInt64LittleEndian(buffer.Slice(2));
                metadataItem = new FileMetadataItem(name, valueLength, valuePointer, totalFileSize);
                bytesConsumed = consumed;
                return true;
            }

        Failure:
            metadataItem = default;
            bytesConsumed = consumed;
            return false;
        }

        public bool TryWriteTo(Span<byte> destination, out int bytesWritten)
        {
            int written = 0;
            if (destination.IsEmpty)
            {
                goto Failure;
            }
            string name = Name ?? string.Empty;
            int length = Encoding.UTF8.GetByteCount(name);
            if (length > 255)
            {
                goto Failure;
            }
            destination[0] = (byte)length;
            destination = destination.Slice(1);
            written++;

            if (destination.Length < length)
            {
                goto Failure;
            }
            EncodingHelper.EncodeUtf8(name, destination.Slice(0, length));
            destination = destination.Slice(length);
            written += length;

            if (destination.Length < 10)
            {
                goto Failure;
            }

            string? inlinedValue = InlinedValue;
            if (inlinedValue is null)
            {
                BinaryPrimitives.WriteUInt16LittleEndian(destination, ValueFieldLength);
                BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(2), ValuePointer);
            }
            else
            {
                length = Encoding.UTF8.GetByteCount(inlinedValue);
                if (length > 8)
                {
                    goto Failure;
                }
                BinaryPrimitives.WriteUInt16LittleEndian(destination, (ushort)length);
                destination.Slice(2, 8).Clear();
                EncodingHelper.EncodeUtf8(inlinedValue, destination.Slice(2, 8));
            }

            written += 10;
            bytesWritten = written;
            return true;

        Failure:
            bytesWritten = written;
            return false;
        }
    }
}
